// SPDX-License-Identifier: GPL-2.0-only

#include <linux/moduleparam.h>
#include <linux/blkdev.h>
#include <linux/ratelimit.h>

#include <scsi/scsi_proto.h>
#include <scsi/scsi_cmnd.h>

#include "scsi_priv.h"
#include "scsi_logging.h"

static bool fail_on_undefined;
module_param(fail_on_undefined, bool, 0400);
MODULE_PARM_DESC(fail_on_undefined,
	"return EIO for all scsi command finished with undefined status");

/** scsi_status_is_sam - check the status respect SAM
 *
 * @status: the status passed up from the driver (including host and
 *          driver components)
 */
static inline int scsi_status_is_illegal(int status)
{
	status &= 0xff;
	switch (status) {
	case SAM_STAT_GOOD:
	case SAM_STAT_CHECK_CONDITION:
	case SAM_STAT_CONDITION_MET:
	case SAM_STAT_BUSY:
	case SAM_STAT_INTERMEDIATE:
	case SAM_STAT_INTERMEDIATE_CONDITION_MET:
	case SAM_STAT_RESERVATION_CONFLICT:
	case SAM_STAT_COMMAND_TERMINATED:
	case SAM_STAT_TASK_SET_FULL:
	case SAM_STAT_ACA_ACTIVE:
	case SAM_STAT_TASK_ABORTED:
		return 0;

	default:
		return 1;
	}
}

/** scsi_fail_on_undefined - enable to fail command finished with undefined status
 * @cmd: the scsi command need check or deal
 * @end_req: the handle used to end request
 */
bool scsi_fail_on_undefined(struct scsi_cmnd *cmd,
		bool (*end_req)(struct request *, blk_status_t, unsigned int))
{
	int result = cmd->result;
	struct request *req = cmd->request;

	if (unlikely(fail_on_undefined) && result && scsi_status_is_illegal(result)) {
		static DEFINE_RATELIMIT_STATE(_rs,
				DEFAULT_RATELIMIT_INTERVAL,
				DEFAULT_RATELIMIT_BURST);
		if (__ratelimit(&_rs))
			scmd_printk(KERN_INFO, cmd, "failed with result 0x%x\n", result);

		if (end_req(req, BLK_STS_IOERR, blk_rq_bytes(req)))
			scmd_printk(KERN_ERR, cmd,
			    "Bytes remaining after failed, no-retry command");
		return true;
	}

	return false;
}
